package com.example.data.permission.mp.interceptor;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.example.data.permission.entity.pojo.UserInfoDTO;
import com.example.data.permission.mp.DataPermissionException;
import com.example.data.permission.mp.permission.DataPermission;
import com.example.data.permission.mp.redis.AuthUtil;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;

/**
 * Created by TD on 2021/7/27
 * 默认实现的数据权限处理器
 */
@Component
public class MyDefaultDataPermissionHandler implements MyDataPermissionHandler {

    // in SQL 目前是 shop_id in ()， 可扩展注解DataPermission，实现ship_id in  () and dept_id in ()
    private static final String WHERE_IN_SQL = " WHERE temp_result_table.{} IN ({}) ";
    // 包装SQL
    private static final String WRAPPER_SQL = " SELECT {} FROM ({}) temp_result_table ";
    // where + 条件 的 SQL
    private static final String WHERE_SQL = " WHERE {} ";


    @Override
    public String getSqlSegment(String originalSql, MappedStatement ms, StatementHandler statementHandler, DataPermission dataPermission) {
        // 1. 获取注解上的信息
        String permissionCode = dataPermission.code();
        String dataPermissionSql = dataPermission.value();
        String selectItems = dataPermission.selectItems();
        // 2. 注解上没有value值，则从数据中查询SQL
        if (StrUtil.isEmpty(dataPermissionSql)) {
            // TODO 这里应该从会话信息中查询，
            UserInfoDTO mrguo = AuthUtil.getUserUserByName("mrguo");
            // 查询他的数据权限信息
            List<com.example.data.permission.entity.DataPermission> dataPermissionList = mrguo.getDataPermissionList();
            // 根据code获取数据库中的数据权限，这个可以使用【code: 权限信息】放在redis或内存中（可优化）
            com.example.data.permission.entity.DataPermission dbPermission = dataPermissionList.stream().filter(permission -> permissionCode.equals(permission.getCode())).findAny().orElse(null);
            Assert.notNull(dbPermission, "数据库没有当前数据权限,code:" + permissionCode);
            dataPermissionSql = dbPermission.getValue();// 数据库中的SQL  where account = #{user.username}
        }
        // 3. 检查 dataPermissionSql，数据库没有，注解也没有，报错
        if (StrUtil.isEmpty(dataPermissionSql)) {
            throw new DataPermissionException("没有查询到dataPermissionSql");
        }
        // 4. 包装SQL返回
        Object parameterObject = statementHandler.getParameterHandler().getParameterObject(); // 参数
        String whereSql = handleDataPermissionSql(ms.getConfiguration(), dataPermissionSql, parameterObject); // 处理参数，返回静态填充了的SQL
        return StrUtil.format(WRAPPER_SQL + StrUtil.format(WHERE_SQL, whereSql), selectItems, originalSql); // 最终执行的SQL
    }

    @Override
    public String getSqlSegment(String originalSql, DataPermission dataPermission) {
        // TODO 这里应该从会话信息中查询，
        UserInfoDTO mrguo = AuthUtil.getUserUserByName("mrguo");
        Long shopId = mrguo.getShopId();
        // TODO 根据用户信息中的门店ID 查询本级及下级所有ID集合
        List<Long> shops = AuthUtil.getShopsById(shopId);
        String shopIds = StringUtils.collectionToCommaDelimitedString(shops);// 集合转为逗号分隔字符串
        String formatWhereSql = StrUtil.format(WHERE_IN_SQL, dataPermission.orgColumn(), shopIds);
        return StrUtil.format(WRAPPER_SQL + formatWhereSql, dataPermission.selectItems(), originalSql); // 最终执行的SQL
    }

    /**
     * 处理 动态数据权限SQL
     */
    private String handleDataPermissionSql(Configuration configuration, String dataPermissionSql, Object parameterObject) {
        // 1. 参照Mybatis源码 创建BoundSql对象
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> clazz = Object.class;
        SqlSource sqlSource = sqlSourceParser.parse(dataPermissionSql, clazz, new HashMap<String, Object>());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        // 2. 处理参数，根据ParameterMapping替换 ?
        if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));

                    } else {
                        throw new DataPermissionException("处理参数异常：" + propertyName);
                    }
                }
            }
        }
        return sql;

    }

    /**
     * 处理输入参数值
     */
    private String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format((Date) obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = " \"\"";
            }
        }
        return value;
    }

}
